const User = require('../models/userModel');
const FavoriteName = require('../models/favoriteNameModel');
const catchAsync = require('../utils/catchAsync');
const jwt = require('jsonwebtoken');
const AppError = require('../utils/appError');
const { promisify } = require('util');
const sendEmail = require('../utils/email');
const crypto = require('crypto');

const createSendToken = (user, statusCode, req, res) => {
  const token = signToken(user._id);
  const cookieOptions = {
    expires: new Date(
      Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
    ),
    httpOnly: true,
    // secure:req.secure || req.headers('x-forwarded-proto') === 'https'
  };
  if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;
  res.cookie('jwt', token, cookieOptions);

  //remove psw from output
  user.password = undefined;

  res.status(statusCode).json({
    status: 'success',
    token,
    data: {
      user,
    },
  });
};

exports.resetPassword = catchAsync(async (req, res, next) => {
  //1)get user based on the token
  const hashedToken = crypto
    .createHash('sha256')
    .update(req.params.token)
    .digest('hex');
  ////the token is the resetToken
  const user = await User.findOne({
    passwordResetToken: hashedToken,
    passwordResetExpires: { $gt: Date.now() },
  });

  //2)if token has not expired,and there is user,set new psw
  if (!user) {
    return next(new AppError('Token is invalid or has expired', 400));
  }
  user.password = req.body.password;
  user.comfirmPassword = req.body.comfirmPassword;
  user.passwordResetToken = undefined;
  user.passwordResetExpires = undefined;
  await user.save();
  //3)update the changedPasswordAt property for the user

  //4)log the user in ,send jwt
  createSendToken(user, 200, req, res);
});
/**
 * the first step is to send the email
 * second is jump to the method -> resetPassword
 */
exports.forgetPassword = catchAsync(async (req, res, next) => {
  const user = await User.findOne({ email: req.body.email });
  if (!user) {
    return next(new AppError('There is no user with email address', 404));
  }
  //2.)generate the user's token
  const resetToken = user.createPassworddResetToken();
  await user.save({ validateBeforeSave: false });
  //3.)send to the user
  const resetUrl = `${req.protocol}://${req.get(
    'host'
  )}/api/v1/users/resetPassword/${resetToken}`;
  const message = `Forget your psw?Submit a PATCH to change you psw to ${resetUrl}\n`;
  try {
    await sendEmail({
      email: user.email,
      subject: `Your psw reset token (valid for 10min)`,
      message,
    });
    res.status(200).json({
      status: 'success',
      message: "Token Send to user's email",
    });
  } catch (err) {
    user.passwordResetToken = undefined;
    user.passwordResetExpires = undefined;
    await user.save({ validateBeforeSave: false });
    next(new AppError('Sending email error,try again', 500));
  }
});
// Only for rendered pages, no errors!
exports.isLoggedIn = async (req, res, next) => {
  if (req.cookies.jwt) {
    try {
      //1.)verify token
      //TODO:
      const decoded = await promisify(jwt.verify)(
        req.cookies.jwt,
        process.env.JWT_SECRET
      );
      // 2) Check if user still exists
      const currentUser = await User.findById(decoded.id);
      if (!currentUser) {
        return next();
      }
      if (currentUser.changedPasswordAfter(decoded.iat)) {
        return next();
      }
      // THERE IS A LOGGED IN USER
      res.locals.user = currentUser;
      return next();
    } catch (err) {
      return next();
    }
  }

  next();
};
/**produce the token when login or signup */
const signToken = (id) => {
  return jwt.sign({ id: id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });
};
//protect
exports.protect = catchAsync(async (req, res, next) => {
  //1.)get the token and check if there
  let token;
  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith('Bearer')
  ) {
    token = req.headers.authorization.split(' ')[1];
    //get the token like :Bearer JSIW2613121476
  } else if (req.cookies.jwt) {
    token = req.cookies.jwt;
  }
  if (!token) {
    return next(new AppError('You are not log in', 401));
  }
  //2.)verification token
  //   jwt.verify(token,process.env.JWT_SECRET)
  const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
  console.log('decoded:' + JSON.stringify(decoded));
  //3.)check if user still exists
  const freshUser = await User.findById(decoded.id);

  if (!freshUser) {
    return next(new AppError('The token  does no longer exists', 401));
  }
  //4.)check if user changed psw after the token was iisued
  if (freshUser.changedPasswordAfter(decoded.iat)) {
    return next(
      new AppError('User recnetly changed password! Please log in again!'),
      401
    );
  }
  // GRANT ACCESS TO PROTECTED ROUTE
  req.user = freshUser;
  //   res.locals.user = freshUser;
  next();
});
exports.signup = catchAsync(async (req, res, next) => {
  const newUser = await User.create({
    name: req.body.name,
    email: req.body.email,
    password: req.body.password,
    comfirmPassword: req.body.comfirmPassword,
    role: req.body.role,
  });

  const token = signToken(newUser._id);
  createSendToken(newUser, 201, req, res);
  const favorite = await FavoriteName.create({
    name: '默认收藏夹',
    user: newUser._id,
    videos: [], // 可选：初始化收藏夹中的视频为空数组
  });
});
exports.login = catchAsync(async (req, res, next) => {
  const { email, password } = req.body;
  if (!email || !password) {
    return next(new AppError('Please provide email and password', 400));
  }
  const user = await User.findOne({ email }).select('+password');
  //get the user(include psw) in the database
  const correct = await user.correctPassword(password, user.password);
  //check the psw if true
  if (!user || !correct) {
    return next(new AppError('Incorrect email or password', 401));
  }
  //get the user's token
  const token = signToken(user._id);
  //3).send to client
  createSendToken(user, 200, req, res);
});
exports.logout = (req, res) => {
  res.cookie('jwt', 'loggedout', {
    expires: new Date(Date.now() + 10 * 1000),
    httpOnly: true,
  });
  res.status(200).json({
    status: 'success',
  });
};

exports.updatePassword = catchAsync(async (req, res, next) => {
  // 1) Get user from collection
  const user = await User.findById(req.user.id).select('+password');
  // 2) Check if POSTed current password is correct
  if (!(await user.correctPassword(req.body.passwordCurrent, user.password))) {
    return next(new AppError('Your current password is wrong.'), 401);
  }
  //3)if so,update psw
  user.password = req.body.password;
  user.comfirmPassword = req.body.comfirmPassword;
  await user.save();
  // User.findByIdAndUpdate() will not work as intented!
  // 1 the validator in model of passwordConfirm only work on create or save
  // 2 pre save middleware also be set
  // 4) Log user in, sent JWT back to user(use new psw)
  createSendToken(user, 200, req, res);
});
exports.restrictTo = (...roles) => {
  return (req, res, next) => {
    //roles ['admin','lead-guide']  role = 'user'
    if (!roles.includes(req.user.role)) {
      ///check if the user was the role
      return next(new AppError('No permission to do this', 403));
    }
  };
  next();
};
